// eee.Sheffield.PZ.Imaging
//
// Copyright ? Ping Zou, 2007
// sg71.cherub@gmail.com

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using eee.Sheffield.PZ.Math;

namespace eee.Sheffield.PZ.Imaging.ImageFilter
{
    /// <summary>
    /// providing some methods to Convert Bitmap to PZMath_matrix
    /// </summary>
    public class ConvertBitmapMatrix
    {
        #region convert methods
        /// <summary>
        /// convert gray level Bitmap image to PZMath_matrix
        /// </summary>
        /// <param name="srcImage">source graylevel Bitmap image, in Format8bppIndexed, with graylevel palette</param>
        /// <returns>dest matrix</returns>
        public PZMath_matrix ConvertGraylevelBitmapToMatrix(Bitmap srcImage)
        {
            if (srcImage.PixelFormat != PixelFormat.Format8bppIndexed)
                throw new ApplicationException("ConvertBitmapMatrix::ConvertGraylevelBitmapToMatrix(), source image is not in Format8bppIndexed.");
            
            int height = srcImage.Height;
            int width = srcImage.Width;
            PZMath_matrix dstMatrix = new PZMath_matrix(height, width);

            // lock source bitmap data
            BitmapData srcData = srcImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
            int offset = srcData.Stride - width;

            unsafe
            {
                byte* src = (byte*)srcData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src ++)
                    {
                        dstMatrix[y, x] = *src;
                    }
                    src += offset;
                }
            }
            srcImage.UnlockBits(srcData);

            return dstMatrix;
        } // ConvertGraylevelBitmapToMatrix()

        /// <summary>
        /// convert colour Bitmap image to PZMath_matrix array
        /// </summary>
        /// <param name="srcImage">source colour Bitmap image, in Format24bppRGB</param>
        /// <returns>dest matrix array</returns>
        public PZMath_matrix[] ConvertColourBitmapToMatrixArray(Bitmap srcImage)
        {
            if (srcImage.PixelFormat != PixelFormat.Format24bppRgb)
                throw new ApplicationException("ConvertBitmapMatrix::ConvertGraylevelBitmapToMatrix(), source image is not in Format24bppRgb.");

            int height = srcImage.Height;
            int width = srcImage.Width;
            PZMath_matrix[] dstMatrixArray = new PZMath_matrix[3];
            dstMatrixArray[0] = new PZMath_matrix(height, width);
            dstMatrixArray[1] = new PZMath_matrix(height, width);
            dstMatrixArray[2] = new PZMath_matrix(height, width);

            // lock source bitmap data
            BitmapData srcData = srcImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            int offset = srcData.Stride - 3 * width;

            unsafe
            {
                byte* src = (byte*)srcData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src += 3)
                    {
                        dstMatrixArray[0][y, x] = *src;
                        dstMatrixArray[1][y, x] = *(src + 1);
                        dstMatrixArray[2][y, x] = *(src + 2);
                    }
                    src += offset;
                }
            }
            srcImage.UnlockBits(srcData);

            return dstMatrixArray;
        } // ConvertColourBitmapToMatrixArray()

        /// <summary>
        /// convert PZMath_matrix to graylevel Bitmap image
        /// </summary>
        /// <param name="srcMatrix">source matrix</param>
        /// <returns>graylevel Bitmap image, in Format8bppIndexed, with graylevel palette</returns>
        public Bitmap ConvertMatrixToGraylevelBitmap(PZMath_matrix srcMatrix)
        {
            return ConvertMatrixToGraylevelBitmap(srcMatrix, false);
        } // ConvertMatrixToGraylevelBitmap()

        /// <summary>
        /// convert PZMath_matrix to graylevel Bitmap image, intensities normalized to [0, 255]
        /// </summary>
        /// <param name="srcMatrix">source matrix</param>
        /// <param name="normalized">bool value</param>
        /// <returns>graylevel Bitmap image, in Format8bppIndexed, with graylevel palette, intensities normalized to [0, 255]</returns>
        public Bitmap ConvertMatrixToGraylevelBitmap(PZMath_matrix srcMatrix, bool normalized)
        {
            int width = srcMatrix.ColumnCount;
            int height = srcMatrix.RowCount;
            Bitmap dstImage = new Bitmap(width, height, PixelFormat.Format8bppIndexed);

            BitmapData dstImageData = dstImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
            int offset = dstImageData.Stride - width;

            // normalize scale [0,255]
            double max = srcMatrix.Max;
            double min = srcMatrix.Min;
            double scale = 255.0 / (max - min);

            unsafe
            {
                byte* dst = (byte*)dstImageData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, dst ++)
                    {
                        if (normalized)
                            *dst = Convert.ToByte((srcMatrix[y, x] - min) * scale);
                        else
                            *dst = Convert.ToByte(srcMatrix[y, x]);
                    }
                    dst += offset;
                }
            }

            dstImage.UnlockBits(dstImageData);

            eee.Sheffield.PZ.Imaging.Image.StandardizeImageTo8bbpIndexed(ref dstImage);
            return dstImage;
        } // ConvertMatrixToGraylevelBitmap()

        /// <summary>
        /// convert PZMath_matrix array to colour Bitmap image
        /// </summary>
        /// <param name="srcMatrix">source matrix array</param>
        /// <returns>colour Bitmap image, in Format24bppRGB</returns>
        public Bitmap ConvertMatrixArrayToColourBitmap(PZMath_matrix[] srcMatrixArray)
        {
            return ConvertMatrixArrayToColourBitmap(srcMatrixArray, false);
        } // ConvertMatrixArrayToColourBitmap()

        /// <summary>
        /// convert PZMath_matrix array to colour Bitmap image, intensities normalized to [0, 255] for each colour channel
        /// </summary>
        /// <param name="srcMatrix">source matrix array</param>
        /// <param name="normalized">bool value</param>
        /// <returns>colour Bitmap image, in Format24bppRGB, intensities normalized to [0, 255] for each colour channel</returns>
        public Bitmap ConvertMatrixArrayToColourBitmap(PZMath_matrix[] srcMatrixArray, bool normalized)
        {
            int width = srcMatrixArray[0].ColumnCount;
            int height = srcMatrixArray[0].RowCount;
            Bitmap dstImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);

            BitmapData dstImageData = dstImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            int offset = dstImageData.Stride - 3 * width;

            PZMath_matrix rChannel = srcMatrixArray[0];
            PZMath_matrix gChannel = srcMatrixArray[1];
            PZMath_matrix bChannel = srcMatrixArray[2];

            // normalize scale [0,255]
            double maxR = rChannel.Max;
            double minR = rChannel.Min;
            double maxG = gChannel.Max;
            double minG = gChannel.Min;
            double maxB = bChannel.Max;
            double minB = bChannel.Min;
            double scaleR = 255.0 / (maxR - minR);
            double scaleG = 255.0 / (maxG - minG);
            double scaleB = 255.0 / (maxB - minB);

            unsafe
            {
                byte* dst = (byte*)dstImageData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, dst += 3)
                    {
                        if (normalized)
                        {
                            dst[RGB.R] = Convert.ToByte((rChannel[y, x] - minR) * scaleR);
                            dst[RGB.G] = Convert.ToByte((gChannel[y, x] - minG) * scaleG);
                            dst[RGB.B] = Convert.ToByte((bChannel[y, x] - minB) * scaleB);
                        }
                        else
                        {
                            dst[RGB.R] = Convert.ToByte(rChannel[y, x]);
                            dst[RGB.G] = Convert.ToByte(gChannel[y, x]);
                            dst[RGB.B] = Convert.ToByte(bChannel[y, x]);
                        }
                    }
                    dst += offset;
                }
            }

            dstImage.UnlockBits(dstImageData);
            return dstImage;
        } // ConvertMatrixArrayToColourBitmap()

        /// <summary>
        /// convert black-and-white Bitmap image to a bool matrix, black - true / white - false
        /// </summary>
        /// <param name="srcImage">source black-and-white image</param>
        /// <returns>bool matrix</returns>
        public bool[,] ConvertBlackAndWhiteBitmapToBoolMatrix(Bitmap srcImage)
        {
            int width = srcImage.Width;
            int height = srcImage.Height;
            bool[,] dstMatrix = new bool[width, height];
            if (eee.Sheffield.PZ.Imaging.Image.IsBlackAndWhite(srcImage))
            {
                BitmapData srcImageData = srcImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, srcImage.PixelFormat);
                int srcOffset = srcImage.PixelFormat == PixelFormat.Format8bppIndexed ? srcImageData.Stride - width : srcImageData.Stride - 3 * width;
                int srcStep = srcImageData.PixelFormat == PixelFormat.Format8bppIndexed ? 1 : 3;
                unsafe
                {
                    byte* src = (byte*)srcImageData.Scan0.ToPointer();
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++, src += srcStep)
                        {
                            if (*src == 255)
                                dstMatrix[x, y] = false;    // white
                            else
                                dstMatrix[x, y] = true;     // black
                        }
                        src += srcOffset;
                    }
                }                
                srcImage.UnlockBits(srcImageData);                
            }
            else
            {
                throw new ApplicationException("ConvertBitmapMatrix::ConvertBlackAndWhiteBitmapToBoolMatrix(), source image is not a black-and-white image.");
            }
            return dstMatrix;
        } // ConvertBlackAndWhiteBitmapToBoolMatrix()

        /// <summary>
        /// get ROI matrix
        /// source image is B/W, Black - 0 - frame; W - 255 - mask
        /// ROI matrix, 0.0 - frame, 1.0 - mask;
        /// </summary>
        /// <param name="srcImage"></param>
        /// <returns></returns>
        public PZMath_matrix GetROIMatrix(Bitmap srcImage)
        {
            int width = srcImage.Width;
            int height = srcImage.Height;
            PZMath_matrix dstMatrix = new PZMath_matrix(height, width);
            if (eee.Sheffield.PZ.Imaging.Image.IsBlackAndWhite(srcImage))
            {
                BitmapData srcImageData = srcImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, srcImage.PixelFormat);
                int srcOffset = srcImage.PixelFormat == PixelFormat.Format8bppIndexed ? srcImageData.Stride - width : srcImageData.Stride - 3 * width;
                int srcStep = srcImageData.PixelFormat == PixelFormat.Format8bppIndexed ? 1 : 3;
                unsafe
                {
                    byte* src = (byte*)srcImageData.Scan0.ToPointer();
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++, src += srcStep)
                        {
                            if (*src == 255)
                                dstMatrix[y, x] = 1.0;    // white - 1.0 - mask
                            else
                                dstMatrix[y, x] = 0.0;     // black - 0.0 - frame
                        }
                        src += srcOffset;
                    }
                }                
                srcImage.UnlockBits(srcImageData);                
            }
            else
            {
                throw new ApplicationException("ConvertBitmapMatrix::GetROIMatrix(), source image is not a black-and-white image.");
            }

            return dstMatrix;
        } // GetROIMatrix()
        #endregion
        
        #region static methods               
        /// <summary>
        /// assume black/white matrix, turn black to input colour
        /// </summary>
        public static Bitmap ToBitmap(PZMath_matrix srcMatrix, Color colour)
        {
            int width = srcMatrix.ColumnCount;
            int height = srcMatrix.RowCount;
            Bitmap dstImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);

            BitmapData dstImageData = dstImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            int stride = dstImageData.Stride;
            int offset = stride - 3 * width;
            Color white = Color.White;

            unsafe
            {
                byte* src = (byte*)dstImageData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src += 3)
                    {
                        double intensity = srcMatrix[y, x];
                        if (intensity == 0)
                        {
                            src[RGB.R] = colour.R;
                            src[RGB.G] = colour.G;
                            src[RGB.B] = colour.B;
                        }
                        else
                        {
                            src[RGB.R] = white.R;
                            src[RGB.G] = white.G;
                            src[RGB.B] = white.B;
                        }
                    }
                    src += offset;
                }
            }

            dstImage.UnlockBits(dstImageData);
            return dstImage;
        } // ToBitmap()
        public static Bitmap ToBitmap(bool[,] srcBoolMatrix)
        {
            int width = srcBoolMatrix.GetLength(0);
            int height = srcBoolMatrix.GetLength(1);
            Bitmap dstImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);

            BitmapData dstImageData = dstImage.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            int stride = dstImageData.Stride;
            int offset = stride - 3 * width;

            unsafe
            {
                byte* src = (byte*)dstImageData.Scan0.ToPointer();
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, src += 3)
                    {
                        if (srcBoolMatrix[x, y])
                        {
                            src[RGB.R] = 0;
                            src[RGB.G] = 0;
                            src[RGB.B] = 0;
                        }
                        else
                        {
                            src[RGB.R] = 255;
                            src[RGB.G] = 255;
                            src[RGB.B] = 255;
                        }                        
                    }
                    src += offset;
                }
            }

            dstImage.UnlockBits(dstImageData);
            return dstImage;
        }
        #endregion
    }
}
